/*
 * universe.js
 */
(function() {

	function create2dArray(x, y) {
		var a = [];
		for (var i = 0; i < x; i++) {
			a[i] = [];
			for (var j = 0; j < y; j++) {
				 a[i][j] = 0;
			}
		}
		return a;
	}

	/*
	 * 815
	 * 4+2
	 * 736
	 */
	var neighborhood = {
		getNeighborhood: function(x, y, filter) {
			return akinao.ca.Neighborhood.get((filter(this.get(x, y - 1))) |
			((filter(this.get(x + 1, y))) << 1) |
			((filter(this.get(x, y + 1))) << 2) |
			((filter(this.get(x - 1, y ))) << 3) |
			((filter(this.get(x + 1, y - 1))) << 4) |
			((filter(this.get(x + 1, y + 1))) << 5) |
			((filter(this.get(x - 1, y + 1))) << 6) |
			((filter(this.get(x - 1, y - 1))) << 7));
		},
	};
	
	/*
	 * UniverseOpened
	 */
	function UniverseOpend(x, y) {
		this.sx = x;
		this.sy = y;
		this.universe = create2dArray(x, y);
	}

	UniverseOpend.prototype = {
		isOut: function(x, y) {
			return 0 > x || 0 > y || this.sx <= x || this.sy <= y;
		},
		
		get: function(x, y) {
			if (this.isOut(x, y)) return 0;
			return this.universe[x][y];
		},
		
		set: function(x, y, v) {
			if (!this.isOut(x, y)) this.universe[x][y] = v;
		},

	};
	
	akinao.lang.extend(UniverseOpend.prototype, neighborhood);
	
	/*
	 * UniverseClosed
	 */
	function UniverseClosed(x, y) {
		this.sx = x;
		this.sy = y;
		this.universe = create2dArray(x, y);
	}
  
	function loop(number, max) {
		if (0 <= number && number < max) return number;
		if (number >= max) return number % max;
		return max - 1 + ((number + 1) % max);	
	}

	UniverseClosed.prototype = {
		isOut: function(x, y) {
			return false;
		},
		
		get: function(x, y) {
			return this.universe[loop(x, this.sx)][loop(y, this.sy)];
		},
		
		set: function(x, y, v) {
			this.universe[loop(x, this.sx)][loop(y, this.sy)] = v;
		},
	};

	akinao.lang.extend(UniverseClosed.prototype, neighborhood);

	/*
	 * GenerationalUniverse
	 */
	function Universe(x, y, closed, rule) {
		this.sx = x;
		this.sy = y;
		var u;
		if (closed) {
			u = [new UniverseClosed(x, y), new UniverseClosed(x, y)];
		} else {
			u = [new UniverseOpend(x, y), new UniverseOpend(x, y)];
		}
		this.universe = u;
		this.now = 0;
		this.targetUniverse = this.universe[this.now];
		this.rule = rule;
	}

	Universe.prototype = {
		isOut: function(x, y) {
			this.targetUniverse.isOut(x, y);
		},
	  
		get: function(x, y) {
			return this.targetUniverse.get(x, y);
		},
	  
		set: function(x, y, v) {
			this.targetUniverse.set(x, y, v);
		},
	  
		next: function(rule) {
			var next = rule ? rule.next : this.rule.next;
			var n = this.universe[this.now];
			var r = this.universe[1 - this.now];
			var sx = this.sx, sy = this.sy;
			for (var x = 0; x < sx; x++) {
				for (var y = 0; y < sy; y++) {
					r.set(x, y, next(x, y, n));
				}
			}
			this.now = 1 - this.now;
			this.targetUniverse = this.universe[this.now];
		},

		getNeighborhood: function(x, y, filter) {
			return this.targetUniverse.getNeighborhood(x, y, filter);
		},
	};

	akinao.lang.namespace("akinao.ca", {
		Universe: Universe,
		UniverseClosed: UniverseClosed,
		UniverseOpend: UniverseOpend,
	});
})();